﻿using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using SobrietyEngine.Guids;
using SobrietyEngine.Actor;
using SobrietyEngine.Components.Common;

namespace SobrietyEngine.Components.SceneGraph
{
    /// <summary>
    /// DONOT USE IF: 
    /// this Cmp wont work for > 1 parent, since all orientations and pos' are currently in Absolute values
    /// will need to fix this up. This *can* work as long as root actor is in absolute but all atached actors use 
    /// relative. seems messy to mix the two methods together fo all actors. requires fixing.
    /// 
    /// A crappy type of scene graph to make it easy to attach actors to other actors
    /// a root is defined by having no parent
    /// not particularly speedy because every call to a leaf results in recalculating all the way down to the root
    /// this isnt ment for aything other than makign it easier to manipualte multiple actors at once, such
    /// as creating 4 actors that attach to a single actor, and control via the root actor
    /// 
    /// if a speedy scenegraph is wanted for rending, this can be done by creating a new Scene type
    /// 
    /// problems: requires actors to have scaleCmp,orientation,position
    /// this should be changed to use identity and (0,0,0) for missing components
    /// this wont work for > 1 parent, since all orientations and pos' are currently in Absolute values
    /// will need to fix this up. 
    /// </summary>
    public class SceneNodeCmp:BaseComponent, ISceneNode
    {
        //we can connect to an actor object as our root node
        CGuid rootActorGuid;

        ISceneNode parent;
        List<ISceneNode> children = new List<ISceneNode>();

        Vector3 pos;
        IPosition position;

        Quaternion orient;
        IOrientation orientation;

        public Boolean InheritScale { get; set; }
        Vector3 scale;
        IScale scaleCmp;

        Matrix transform;

        public SceneNodeCmp()
            : base()
        {
            InheritScale = true;
        }
        
        public SceneNodeCmp(ISceneNode _parent)
        {
            parent = _parent;
            parent.AddChild(this);
        }

        public SceneNodeCmp(CGuid parentActor)
        {
            rootActorGuid = parentActor;
        }

        public override bool InitComponent()
        {
            if (rootActorGuid != null)
            {
                IActor rootActor = rootActorGuid.Resolve<IActor>();
                ISceneNode rootNode = rootActor.Get<ISceneNode>();
                if (rootNode == null)
                {
                    rootNode = new SceneNodeCmp();
                    rootActor.AttachComponent((IComponent)rootNode);                    
                }
                parent = rootNode;
            }

            position = Actor.Get<IPosition>();
            orientation = Actor.Get<IOrientation>();
            scaleCmp = Actor.Get<IScale>();

            pos = position != null ? position.PositionV3 : Vector3.Zero;
            orient = orientation != null ? orientation.Orientation : Quaternion.Identity;
            scale = scaleCmp != null && InheritScale ? scaleCmp.Scale : Vector3.One;
            transform = Matrix.CreateScale(scale) *
                        Matrix.CreateFromQuaternion(orient) *
                        Matrix.CreateTranslation(pos);
            
            //in case we were added to an actor post init, we need to alert the graphic component
            //this is hacky. we should probably use events to connectthis stuff. but alas, here we are
            SobrietyEngine.Components.Graphic.Base3DGraphic b3d = (SobrietyEngine.Components.Graphic.Base3DGraphic)Actor.Get<IGraphic3D>();
            if (b3d != null && b3d.Initialized)
            {
                b3d.SetSceneNode(this);
            }

            base.InitComponent();
            return true;
        }

        bool dirty = false;
        public override void Update(GameTime ElapsedTime)
        {            
            if (position != null)
                if (pos != position.PositionV3)
                {
                    pos = position.PositionV3;
                    dirty = true;
                }

            if (orientation != null)
                if (orient != orientation.Orientation)
                {
                    orient = orientation.Orientation;
                    dirty = true;
                }

            if(scaleCmp!=null && InheritScale)
                if (scale != scaleCmp.Scale)
                {
                    scale = scaleCmp.Scale;
                    dirty = true;
                }

            if (dirty)
            {
                transform = Matrix.CreateScale(scale) *
                    Matrix.CreateFromQuaternion(orient) *
                    Matrix.CreateTranslation(pos);
                dirty = false;
            }
        }

        public void AddChild(ISceneNode child)
        {
            children.Add(child);
        }

        public Quaternion GetAbsoluteOrientation()
        {
            if (parent == null)            
                return orient;
            else
                return parent.GetAbsoluteOrientation() * orient;
        }

        public Vector3 GetAbsolutePosition()
        {
            if (parent == null)
                return pos;
            else
                return parent.GetAbsolutePosition() + pos;
        }

        public Vector3 GetAbsoluteScale()
        {
            if (parent == null)
                return scale;
            else
                return parent.GetAbsoluteScale() * scale;
        }

        public Matrix GetAbsoluteTransformation()
        {
            if (parent == null)
                return transform;
            else
                return parent.GetAbsoluteTransformation() * transform;
        }

        public Matrix GetAbsoluteParentTransformation()
        {
            if (parent == null)
                return Matrix.Identity;
            else
                return parent.GetAbsoluteTransformation();
        }

        override public Type[] SupportedInterfaces()
        {
            return new Type[] {typeof(ISceneNode)};
        }
    }
}
